﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml.Linq;
using MyVocabulary.App_Code;
using MyVocabulary.Popup;

namespace MyVocabulary
{
    public partial class VocabularyTest : Form
    {
        #region Consts

        protected static class FormMessages
        {
            public const string ErrorMessageTitle = "Error";
            public const string EmptyTest = "The test contains no words.";
            public const string EmptyFailedWordsList = "No failed word to retest.";

            public const string CompletedTestTitle = "Completed Test";
            public const string CompletedTestMsg = "You covered all the questions in this test. Press the \"Restart test\" if you want to start all over again.";

            public const string ErrorExportingFailedWords = "An error occurred while exporting the list of failed words. Details: {0}.";
            public const string ErrorExportingRemainingWords = "An error occurred while exporting the list of remaining words. Details: {0}.";

            public const string AnswerTooltip =
                "Complete here the translation of the word you see in the green textbox above.";
            public const string CorrectAnswerTooltip = "Your answer is correct";
            public const string IncorrectAnswerTooltip = "Your answer \"{0}\" is incorrect.";
        }

        public static class BorderColors
        {
            public static System.Drawing.Color WaitingForAnswer = System.Drawing.SystemColors.InactiveCaptionText;
            public static System.Drawing.Color CorrectAnswer = System.Drawing.Color.DarkGreen;
            public static System.Drawing.Color WrongAnswer = System.Drawing.Color.Tomato;
        }

        #endregion

        #region Fields

        /// <summary>
        /// The path to the file that contains the vocabulary
        /// </summary>
        protected string FilePath { get; set; }

        /// <summary>
        /// The list of the words to be tested
        /// </summary>
        protected List<VocabularyWord> testWords;

        /// <summary>
        /// The list of the words the user answered correctly
        /// </summary>
        protected List<VocabularyWord> answeredWords;

        /// <summary>
        /// The list of the words the user answered incorrectly
        /// </summary>
        protected List<VocabularyWord> failedWords;

        /// <summary>
        /// The word that is currently displayed
        /// </summary>
        protected VocabularyWord currentWord;

        /// <summary>
        /// True if the user clicked the "Answer" button for the current word
        /// </summary>
        protected bool currentWordIsAnswered;

        /// <summary>
        /// True if the user answered correctly and pressed the "Next" button for the first time for the current word 
        /// while cbShowDetails is checked. (This is used for showing details for correctly answered questions).
        /// </summary>
        protected bool currentWordIsCorrectlyAnswered;

        #endregion

        public VocabularyTest(List<VocabularyWord> testWords, string FilePath)
        {
            InitializeComponent();

            this.testWords = testWords;
            answeredWords = new List<VocabularyWord>();
            failedWords = new List<VocabularyWord>();
            this.FilePath = FilePath;

            StartTest();
        }

        /// <summary>
        /// Initialize a new test using all the words in the vocabulary
        /// </summary>
        public void StartTest()
        {
            //Reset the lists
            MoveCollectionElements(answeredWords, testWords);
            MoveCollectionElements(failedWords, testWords);

            //Get the first word to test
            var word = GetNextWord(testWords);
            if (word == null)
            {
                (new CustomMessageBox(FormMessages.EmptyTest, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK))
                    .ShowDialog();

                btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = btnRetestFailed.Enabled = false;
                lnkSaveFailedInNewFile.Visible = false;
                lnkSaveRemainingInNewFile.Visible = false;
                return;
            }
            currentWord = word;
            currentWordIsAnswered = false;
            currentWordIsCorrectlyAnswered = false;

            //Set form values
            tbWord.Text = word.Translation;

            pnlAnswer.BackColor = BorderColors.WaitingForAnswer;
            tbAnswer.Text = string.Empty;            
            tbAnswer.Focus();            
            toolTip1.SetToolTip(tbAnswer, FormMessages.AnswerTooltip);

            tbPartOfSpeech.Text = string.Empty;
            tbExamples.Text = string.Empty;
            tbRemarks.Text = string.Empty;
            btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = true;

            SetStatistics(testWords.Count, answeredWords.Count, failedWords.Count);
        }

        /// <summary>
        /// Move all the items that exist in the source list into the destination list
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        protected static void MoveCollectionElements(List<VocabularyWord> source, List<VocabularyWord> destination)
        {
            foreach (var item in source)
            {
                destination.Add(item);                
            }
            source.RemoveAll(item => 1 == 1);
        }
        
        /// <summary>
        /// Get the next word to test or null if the list is empty
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        protected static VocabularyWord GetNextWord(List<VocabularyWord> list)
        {
            if (list.Count == 0)
                return null;

            var rand = new Random();
            return list[rand.Next(0, list.Count - 1)];
        }

        /// <summary>
        /// Set the properties for the form controls found on the "Statistics" group
        /// </summary>
        /// <param name="noRemining"></param>
        /// <param name="noCorrect"></param>
        /// <param name="noFailed"></param>
        public void SetStatistics(int noRemining, int noCorrect, int noFailed)
        {
            var noTotal = noRemining + noCorrect + noFailed;
            lbNoOfQuestions.Text = string.Format("{0} / {1}", noRemining, noTotal);
            lbNoCorrect.Text = string.Format("{0} ({1:0.00}%)", noCorrect, (100.0 * noCorrect) / noTotal);
            lbNoFailed.Text = string.Format("{0} ({1:0.00}%)", noFailed, (100.0 * noFailed) / noTotal);

            btnRetestFailed.Enabled = lnkSaveFailedInNewFile.Visible = (noFailed > 0);
            lnkSaveRemainingInNewFile.Visible = (noRemining > 0);
        }

        /// <summary>
        /// Return true if the words are equal when character casing and whitespaces are ignored
        /// </summary>
        /// <param name="correctWord"></param>
        /// <param name="answer"></param>
        /// <returns></returns>
        protected static bool AnswerIsCorrect(string correctWord, string answer)
        {
            var pattern = new Regex(@"\s+");
            return string.Compare(pattern.Replace(correctWord, " "), pattern.Replace(answer, " "), true) == 0;
        }

        /// <summary>
        /// Set control properties when all words are tested
        /// </summary>
        protected void StopTest()
        {
            btnAnswer.Enabled = btnNext.Enabled = false;
            SetStatistics(testWords.Count, answeredWords.Count, failedWords.Count);

            (new CustomMessageBox(FormMessages.CompletedTestMsg, FormMessages.CompletedTestTitle,
                                  CustomMessageBoxButtons.OK)).ShowDialog();
        }

        /// <summary>
        /// Fix multiline textboxes that do not show new lines
        /// </summary>
        /// <param name="tb"></param>
        /// <param name="text"></param>
        protected static void SetText(TextBox tb, string text)
        {
            if (tb.Multiline)
            {
                tb.Lines = (new Regex(@"\n|\r\n").Split(text));
            }
            else
            {
                tb.Text = text;
            }
        }

        #region Buttons

        /// <summary>
        /// Show the correct answer and the details for the current word.
        /// If the user did not complete a correct answer increase the no of failed answers
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAnswer_Click(object sender, EventArgs e)
        {
            if (!currentWordIsAnswered && !currentWordIsCorrectlyAnswered)
            {
                //The user did not use the 'Answer' or the 'Next'buttons for the current word
                currentWordIsAnswered = true;
                if (AnswerIsCorrect(currentWord.Word, tbAnswer.Text))
                {
                    answeredWords.Add(currentWord);
                    testWords.Remove(currentWord);
                    pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                    toolTip1.SetToolTip(tbAnswer, FormMessages.CorrectAnswerTooltip);
                }
                else
                {
                    toolTip1.SetToolTip(tbAnswer, string.Format(FormMessages.IncorrectAnswerTooltip, tbAnswer.Text));
                    tbAnswer.Text = currentWord.Word;
                    pnlAnswer.BackColor = BorderColors.WrongAnswer;
                    failedWords.Add(currentWord);
                    testWords.Remove(currentWord);                    
                }
                SetStatistics(testWords.Count, answeredWords.Count, failedWords.Count);  
            }
            else
                tbAnswer.Text = currentWord.Word;

            tbPartOfSpeech.Text = currentWord.PartOfSpeech;
            SetText(tbExamples, currentWord.Examples);
            SetText(tbRemarks, currentWord.Remarks);
        }

        /// <summary>
        /// Show a new word only if the user answerd correctly otherwise mark the textbox with a red border
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNext_Click(object sender, EventArgs e)
        {
            if (currentWordIsCorrectlyAnswered || currentWordIsAnswered || AnswerIsCorrect(currentWord.Word, tbAnswer.Text))
            {
                if(!currentWordIsCorrectlyAnswered && !currentWordIsAnswered && cbShowDetails.Checked)
                {
                    //Show details for a new correct answer
                    pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                    toolTip1.SetToolTip(tbAnswer, FormMessages.CorrectAnswerTooltip);
                    tbPartOfSpeech.Text = currentWord.PartOfSpeech;
                    SetText(tbExamples, currentWord.Examples);
                    SetText(tbRemarks, currentWord.Remarks);
                    currentWordIsCorrectlyAnswered = true;
                }
                else
                { 
                    if(!currentWordIsAnswered) //this means the word was not previously removed from the list using the "Answer" button
                    {
                        answeredWords.Add(currentWord);
                        testWords.Remove(currentWord);
                    }

                    //Move to the next question
                    currentWord = GetNextWord(testWords);
                    if (currentWord == null)
                    {
                        if (!currentWordIsAnswered)
                            pnlAnswer.BackColor = BorderColors.CorrectAnswer;
                        StopTest();
                    }
                    else
                    {
                        currentWordIsAnswered = false;
                        currentWordIsCorrectlyAnswered = false;

                        //Set form values
                        tbWord.Text = currentWord.Translation;
                        pnlAnswer.BackColor = BorderColors.WaitingForAnswer;
                        tbAnswer.Text = string.Empty;
                        tbAnswer.Focus();
                        toolTip1.SetToolTip(tbAnswer, FormMessages.AnswerTooltip);
                        tbPartOfSpeech.Text = string.Empty;
                        tbExamples.Text = string.Empty;
                        tbRemarks.Text = string.Empty;
                        btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = true;

                        SetStatistics(testWords.Count, answeredWords.Count, failedWords.Count);
                    }
                }                
            }
            else
            {
                toolTip1.SetToolTip(tbAnswer, string.Format(FormMessages.IncorrectAnswerTooltip, tbAnswer.Text));
                tbAnswer.SelectAll();
                tbAnswer.Focus();
                pnlAnswer.BackColor = BorderColors.WrongAnswer;
            }
        }

        /// <summary>
        /// Restart the test from the beginning
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRetest_Click(object sender, EventArgs e)
        {
            StartTest();
        }

        /// <summary>
        /// Check only the unanswerd and failed words
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRetestFailed_Click(object sender, EventArgs e)
        {            
            if(failedWords.Count == 0)
            {
                (new CustomMessageBox(FormMessages.EmptyFailedWordsList, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK))
                    .ShowDialog();

                btnRetestFailed.Enabled = lnkSaveFailedInNewFile.Visible = false;
                return;                
            }

            //Move all failed words in the initial list
            MoveCollectionElements(failedWords, testWords);

            //Get the first word to test
            currentWord = GetNextWord(testWords); 
            currentWordIsAnswered = false;
            currentWordIsCorrectlyAnswered = false;

            //Set form values
            tbWord.Text = currentWord.Translation;
            pnlAnswer.BackColor = BorderColors.WaitingForAnswer;
            tbAnswer.Text = string.Empty;
            tbAnswer.Focus();
            toolTip1.SetToolTip(tbAnswer, FormMessages.AnswerTooltip);
            tbPartOfSpeech.Text = string.Empty;
            tbExamples.Text = string.Empty;
            tbRemarks.Text = string.Empty;
            btnAnswer.Enabled = btnNext.Enabled = btnRetest.Enabled = true;

            SetStatistics(testWords.Count, answeredWords.Count, failedWords.Count);
        }

        private void lnkSaveFailedOrRemainingInNewFile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            var saveFailed = (sender == lnkSaveFailedInNewFile);
            var list = saveFailed ? failedWords : testWords;

            var file = new FileInfo(FilePath);
            if (file.Exists)
            {
                saveFileDialog1.InitialDirectory = file.DirectoryName;
                saveFileDialog1.FileName = (saveFailed ? "FailedWordsFor" : "RemainingWordsFor") +
                                           (new Regex("\\.xml$").Replace(file.Name, string.Empty));
            }

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    //create the file                  
                    var doc = new XDocument();
                    var element = new XElement("DocumentElement");
                    doc.AddFirst(element);

                    foreach (var word in list)
                    {                        
                        var row = new XElement(VocabularyWordConst.VocabularyWord);
                        row.Add(new XElement(VocabularyWordConst.Word, word.Word));
                        row.Add(new XElement(VocabularyWordConst.Translation, word.Translation));
                        row.Add(new XElement(VocabularyWordConst.PartOfSpeech, word.PartOfSpeech));
                        row.Add(new XElement(VocabularyWordConst.Examples, word.Examples));
                        row.Add(new XElement(VocabularyWordConst.Remarks, word.Remarks));
                        element.Add(row);
                    }

                    //Save the modifications
                    doc.Save(saveFileDialog1.FileName);     
                }
                catch (Exception exp)
                {
                    (new CustomMessageBox(
                        string.Format(
                            saveFailed
                                ? FormMessages.ErrorExportingFailedWords
                                : FormMessages.ErrorExportingRemainingWords, exp.Message),
                        FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK)).ShowDialog(this);
                }
            }
        }

        #endregion
    }
}
